@_spi(Private) @testable import Sentry
@_spi(Private) import SentryTestUtils
import XCTest

#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
class SentryAppStateManagerTests: XCTestCase {
    private static let dsnAsString = TestConstants.dsnAsString(username: "SentryOutOfMemoryTrackerTests")

    private class Fixture {

        let options: Options
        let fileManager: SentryFileManager
        let currentDate = TestCurrentDateProvider()
        let dispatchQueue = TestSentryDispatchQueueWrapper()
        let notificationCenterWrapper = TestNSNotificationCenterWrapper()

        init() throws {
            options = Options()
            options.dsn = SentryAppStateManagerTests.dsnAsString
            options.releaseName = TestData.appState.releaseName
            
            fileManager = try XCTUnwrap(SentryFileManager(
                options: options,
                dateProvider: currentDate,
                dispatchQueueWrapper: dispatchQueue
            ))
        }

        func getSut() -> SentryAppStateManager {
            SentryDependencyContainer.sharedInstance().sysctlWrapper = TestSysctl()
            SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = TestSentryDispatchQueueWrapper()
            SentryDependencyContainer.sharedInstance().notificationCenterWrapper = notificationCenterWrapper
            return SentryAppStateManager(
                releaseName: options.releaseName,
                crashWrapper: TestSentryCrashWrapper(processInfoWrapper: ProcessInfo.processInfo),
                fileManager: fileManager,
                sysctlWrapper: SentryDependencyContainer.sharedInstance().sysctlWrapper
            )
        }
    }

    private var fixture: Fixture!
    private var sut: SentryAppStateManager!

    override func setUpWithError() throws {
        try super.setUpWithError()

        fixture = try Fixture()
        sut = fixture.getSut()
    }

    override func tearDown() {
        super.tearDown()
        fixture.fileManager.deleteAppState()
        clearTestState()
    }

    func testStartStoresAppState() {
        XCTAssertNil(fixture.fileManager.readAppState())

        sut.start()
        XCTAssertNotNil(fixture.fileManager.readAppState())
    }

    func testStartOnlyRunsLogicWhenStartCountBecomesOne() {
        XCTAssertNil(fixture.fileManager.readAppState())

        sut.start()
        XCTAssertNotNil(fixture.fileManager.readAppState())

        fixture.fileManager.deleteAppState()

        sut.start()
        XCTAssertNil(fixture.fileManager.readAppState())
    }

    func testStopDoesNotDeleteAppState() {
        XCTAssertNil(fixture.fileManager.readAppState())

        sut.start()
        XCTAssertNotNil(fixture.fileManager.readAppState())

        sut.stop()
        XCTAssertNotNil(fixture.fileManager.readAppState())
    }

    func testStopUpdatesAppState() {
        sut.start()

        let stateBeforeStop = fixture.fileManager.readAppState()
        XCTAssertTrue(stateBeforeStop!.isSDKRunning)

        sut.stop(withForce: true)

        let stateAfterStop = fixture.fileManager.readAppState()
        XCTAssertFalse(stateAfterStop!.isSDKRunning)
    }

    func testForcedStop() {
        XCTAssertNil(fixture.fileManager.readAppState())

        sut.start()
        sut.start()
        sut.start()

        sut.stop()
        XCTAssertEqual(sut.startCount, 2)

        sut.stop(withForce: true)
        XCTAssertEqual(sut.startCount, 0)

        XCTAssertEqual(fixture.notificationCenterWrapper.removeObserverWithNameAndObjectInvocations.count, 4)
    }

    func testUpdateAppState() {
        sut.storeCurrentAppState()

        XCTAssertFalse(fixture.fileManager.readAppState()!.wasTerminated)

        sut.updateAppState { state in
            state.wasTerminated = true
        }

        XCTAssertEqual(fixture.fileManager.readAppState()!.wasTerminated, true)
    }
}
#endif
